/*
 * Copyright (c) 2020 hebeiyiliuba.com
 * All rights reserved.
 *
 */
package org.jeecg.common.util;

import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.io.*;
import java.nio.file.Files;
import java.util.Iterator;

/**
 * 在这里编写类的功能描述
 *
 * @author duanlikao
 * @created 2021/3/13
 */
@Slf4j
public class ImageCompressUtil {




    /**
     *通过图片base64流判断图片等于多少字节
     *image 图片流
     */
    public static Integer imageSize(String image){
       // String str = image.substring(22); // 1.需要计算文件流大小，首先把头部的data:image/png;base64,（注意有逗号）去掉。
        Integer equalIndex= image.indexOf("=");//2.找到等号，把等号也去掉
        if(image.indexOf("=")>0) {
            image = image.substring(0, equalIndex);
        }
        Integer strLength = image.length();//3.原来的字符流大小，单位为字节
        Integer size = strLength-(strLength/8)*2;//4.计算后得到的文件流大小，单位为字节
        size  =  (size/1024)+1 ;//转换单位为kb
//        log.info("ImageCompressUtil imageSize imgFile size :{}",size);
        return size;
    }

    /**
     * 直接指定压缩后的宽高：
     * (先保存原文件，再压缩、上传)
     * 用于二维码压缩
     * @param oldFile 要进行压缩的文件全路径
     * @param width 压缩后的宽度
     * @param height 压缩后的高度
     * @param quality 压缩质量
     * @param smallIcon 文件名的小小后缀(注意，非文件后缀名称),入压缩文件名是yasuo.jpg,则压缩后文件名是yasuo(+smallIcon).jpg
     * @return 返回压缩后的文件的全路径
     */
    public static String zipImageFile(String oldFile, int width, int height,
            float quality, String smallIcon) {
        if (oldFile == null) {
            return null;
        }
        String newImage = null;
        try {
            /**对服务器上的临时文件进行处理 */
            Image srcFile = ImageIO.read(new File(oldFile));
            /** 宽,高设定 */
            BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            tag.getGraphics().drawImage(srcFile, 0, 0, width, height, null);
            String filePrex = oldFile.substring(0, oldFile.indexOf('.'));
            /** 压缩后的文件名 */
            newImage = filePrex + smallIcon + oldFile.substring(filePrex.length());
            /** 压缩之后临时存放位置 */
            FileOutputStream out = new FileOutputStream(newImage);
//            JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
//            JPEGEncodeParam jep = JPEGCodec.getDefaultJPEGEncodeParam(tag);
//            /** 压缩质量 */
//            jep.setQuality(quality, true);
//            encoder.encode(tag, jep);
            out.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return newImage;
    }

    /**
     * 保存文件到服务器临时路径(用于文件上传)
     * @param fileName
     * @param is
     * @return 文件全路径
     */
    public static String writeFile(String fileName, InputStream is) {
        if (fileName == null || fileName.trim().length() == 0) {
            return null;
        }
        try {
            /** 首先保存到临时文件 */
            FileOutputStream fos = new FileOutputStream(fileName);
            byte[] readBytes = new byte[512];// 缓冲大小
            int readed = 0;
            while ((readed = is.read(readBytes)) > 0) {
                fos.write(readBytes, 0, readed);
            }
            fos.close();
            is.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return fileName;
    }

    /**
     * 等比例压缩算法：
     * 算法思想：根据压缩基数和压缩比来压缩原图，生产一张图片效果最接近原图的缩略图
     * @param srcURL 原图地址
     * @param deskURL 缩略图地址
     * @param comBase 压缩基数
     * @param scale 压缩限制(宽/高)比例  一般用1：
     * 当scale>=1,缩略图height=comBase,width按原图宽高比例;若scale<1,缩略图width=comBase,height按原图宽高比例
     * @throws Exception
     * @author shenbin
     * @createTime 2014-12-16
     * @lastModifyTime 2014-12-16
     */
    public static void saveMinPhoto(String srcURL, String deskURL, double comBase,
            double scale) throws Exception {
        File srcFile = new java.io.File(srcURL);
        Image src = ImageIO.read(srcFile);
        int srcHeight = src.getHeight(null);
        int srcWidth = src.getWidth(null);
        int deskHeight = 0;// 缩略图高
        int deskWidth = 0;// 缩略图宽
        double srcScale = (double) srcHeight / srcWidth;
        /**缩略图宽高算法*/
        if ((double) srcHeight > comBase || (double) srcWidth > comBase) {
            if (srcScale >= scale || 1 / srcScale > scale) {
                if (srcScale >= scale) {
                    deskHeight = (int) comBase;
                    deskWidth = srcWidth * deskHeight / srcHeight;
                } else {
                    deskWidth = (int) comBase;
                    deskHeight = srcHeight * deskWidth / srcWidth;
                }
            } else {
                if ((double) srcHeight > comBase) {
                    deskHeight = (int) comBase;
                    deskWidth = srcWidth * deskHeight / srcHeight;
                } else {
                    deskWidth = (int) comBase;
                    deskHeight = srcHeight * deskWidth / srcWidth;
                }
            }
        } else {
            deskHeight = srcHeight;
            deskWidth = srcWidth;
        }
        BufferedImage tag = new BufferedImage(deskWidth, deskHeight, BufferedImage.TYPE_3BYTE_BGR);
        tag.getGraphics().drawImage(src, 0, 0, deskWidth, deskHeight, Color.white,null); //绘制缩小后的图
        FileOutputStream deskImage = new FileOutputStream(deskURL); //输出到文件流
//        JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(deskImage);
//        encoder.encode(tag); //近JPEG编码
        deskImage.close();
    }

    /**
     * 压缩图片（通过降低图片质量）
     * @explain 压缩图片,通过压缩图片质量，保持原图大小
     * @param quality 图片质量（0-1）
     * @return byte[] 压缩后的图片（jpg）
     * @throws
     */
    public static byte[] compressPicByQuality(byte[] imgByte, float quality) {
        byte[] imgBytes = null;
        try {
            ByteArrayInputStream byteInput = new ByteArrayInputStream(imgByte);
            BufferedImage image = ImageIO.read(byteInput);

            // 如果图片空，返回空
            if (image == null) {
                return null;
            }
            // 得到指定Format图片的writer（迭代器）
            Iterator <ImageWriter> iter = ImageIO.getImageWritersByFormatName("jpeg");
            // 得到writer
            ImageWriter writer = (ImageWriter) iter.next();
            // 得到指定writer的输出参数设置(ImageWriteParam )
            ImageWriteParam iwp = writer.getDefaultWriteParam();
            // 设置可否压缩
            iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
            // 设置压缩质量参数
            iwp.setCompressionQuality(quality);

            iwp.setProgressiveMode(ImageWriteParam.MODE_DISABLED);

            ColorModel colorModel = ColorModel.getRGBdefault();
            // 指定压缩时使用的色彩模式
            iwp.setDestinationType(
                    new javax.imageio.ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(16, 16)));

            // 开始打包图片，写入byte[]
            // 取得内存输出流
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            IIOImage iIamge = new IIOImage(image, null, null);

            // 此处因为ImageWriter中用来接收write信息的output要求必须是ImageOutput
            // 通过ImageIo中的静态方法，得到byteArrayOutputStream的ImageOutput
            writer.setOutput(ImageIO.createImageOutputStream(byteArrayOutputStream));
            writer.write(null, iIamge, iwp);
            imgBytes = byteArrayOutputStream.toByteArray();
        } catch (IOException e) {
            System.out.println("write errro");
            e.printStackTrace();
        }
        return imgBytes;
    }

    public static byte[] compressPicByQuality1(File file, float quality) {
        byte[] inByte = null;
        try {
            BufferedImage image = ImageIO.read(file);

            // 如果图片空，返回空
            if (image == null) {
            return null;
            }
            // 得到指定Format图片的writer
            Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName("jpeg");// 得到迭代器
            ImageWriter writer = (ImageWriter) iter.next(); // 得到writer

            // 得到指定writer的输出参数设置(ImageWriteParam )
            ImageWriteParam iwp = writer.getDefaultWriteParam();
            iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); // 设置可否压缩
            iwp.setCompressionQuality(quality); // 设置压缩质量参数

            iwp.setProgressiveMode(ImageWriteParam.MODE_DISABLED);

            ColorModel colorModel = ColorModel.getRGBdefault();
            // 指定压缩时使用的色彩模式
            iwp.setDestinationType(new javax.imageio.ImageTypeSpecifier(colorModel,
                                colorModel.createCompatibleSampleModel(16, 16)));

            // 开始打包图片，写入byte[]
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // 取得内存输出流
            IIOImage iIamge = new IIOImage(image, null, null);

            // 此处因为ImageWriter中用来接收write信息的output要求必须是ImageOutput
            // 通过ImageIo中的静态方法，得到byteArrayOutputStream的ImageOutput
            writer.setOutput(ImageIO.createImageOutputStream(byteArrayOutputStream));
            writer.write(null, iIamge, iwp);
            inByte = byteArrayOutputStream.toByteArray();
        } catch (IOException e) {
            System.out.println("write errro");
            e.printStackTrace();
        }
        return inByte;
    }
    /**
     * <dependency>
     *   <groupId>net.coobird</groupId>
     *   <artifactId>thumbnailator</artifactId>
     *   <version>[0.4, 0.5)</version>
     * </dependency>
     *
    * 根据指定大小压缩图片
    * @param imageBytes 源图片字节数组
     *  @param quality 图片质量
    * @return 压缩质量后的图片字节数组
    */
    public static byte[] compressPicForScale(byte[] imageBytes ,float quality  ) {
        if (imageBytes == null || imageBytes.length <= 0 ) {
            return imageBytes;
        }
        long srcSize = imageBytes.length;

        try {
            ByteArrayInputStream inputStream = new ByteArrayInputStream(imageBytes);
            ByteArrayOutputStream outputStream= new ByteArrayOutputStream(imageBytes.length);
            Thumbnails.of(inputStream).scale(1f).outputQuality(quality).toOutputStream(outputStream);
            imageBytes = outputStream.toByteArray();
//            log.info("ImageCompressUtil compressPicForScale | 图片原大小={}kb | 压缩后大小={}kb",srcSize / 1024,imageBytes.length / 1024);
        } catch (Exception e) {
            log.error("ImageCompressUtil compressPicForScale msg=图片压缩失败!", e);
        }
        return imageBytes;
    }

    /**
     * 压缩图片
     */
    public static byte[] imageCompression(String path ,double qual ){
        ByteArrayOutputStream out = null;
        try {
            File file = new File(path);
            long length = file.length() / 1024;
            if (length < 500){
                byte[] fileByte = Files.readAllBytes(file.toPath());
                return fileByte;
            }
            do {
                byte[] fileByte = Files.readAllBytes(file.toPath());
                String imgBase64 =  Base64.encodeBase64String(fileByte);
                out = new ByteArrayOutputStream();
                Thumbnails.of(imgBase64)
                       // .scalingMode(ScalingMode.BICUBIC)
                        .scale(1d)
                        .outputQuality(qual)
                        .toOutputStream(out);
                qual = qual - 0.1d;
            }while ((out.size()/1024) > 500 && qual > 0d);

            return out.toByteArray();
        } catch (IOException e) {
            log.error("ImageCompressUtil imageCompression 图片压缩失败",e.getMessage());
        }finally {
            if (out!=null){
                try {
                    out.close();
                } catch (IOException e) {
                    log.error("ImageCompressUtil imageCompression 关闭流失败",e.getMessage());
                }
            }
        }
        return null;
    }


    /**
     * 将图片转换成Base64编码
     * @param imgFile 待处理图片
     * @return
     */
    public static String imageToBase64(String imgFile){
        if(StringUtils.isEmpty(imgFile) ){
            return StringUtils.EMPTY;
        }
        File file = new File(imgFile);
        if(!file.isFile()){
            return StringUtils.EMPTY;
        }
        return imageToBase64(file);
    }

    public static String imageToBase64(File file){

        String imgBase64 = "";
        InputStream in = null;
        byte[] data = null;
        //读取图片字节数组
        try
        {
            in = new FileInputStream(file);
            data = new byte[in.available()];
            in.read(data);
            in.close();
        }catch (IOException e){
            e.printStackTrace();
            return "";
        }
        imgBase64 = new String(Base64.encodeBase64(data));
        return "data:image/jpg;base64,"+imgBase64;
    }

    /**
     * 递归压缩图片
     * @param desPath
     * @param desFileSize
     * @param accuracy
     * @throws IOException
     */
    public static File commpressPicCycle(String srcPath,String desPath , long desFileSize, double accuracy) throws IOException{

        File srcFile = new File(srcPath);
        long fileSize = srcFile.length();
        //判断大小,如果小于500k,不压缩,如果大于等于500k,压缩
        if(fileSize <= desFileSize * 1024){
            return srcFile;
        }
        File desFile = new File(desPath);
        return commpressPicCycle(srcFile,desFile,desFileSize,accuracy);
    }

    public static File commpressPicCycle(File srcFile,File desFile , long desFileSize, double accuracy) throws IOException{
        Thumbnails.of(srcFile).outputQuality(accuracy).scale(accuracy).toFile(desFile);
        long fileSize = desFile.length();
        //判断大小,如果小于500k,不压缩,如果大于等于500k,压缩
        if(fileSize <= desFileSize * 1024){
            return desFile;
        }
        //如果不满足要求,递归直至满足小于1M的要求
        return commpressPicCycle(desFile,desFile, desFileSize, accuracy);
    }

    public static File commpressPicCycle2(String srcPath , long desFileSize, double accuracy) throws IOException{

        File srcFile = new File(srcPath);
        long srcFileSize = srcFile.length();
        if(srcFileSize <= desFileSize * 1024){
            return srcFile;
        }
       return commpressPicCycle2(srcFile, desFileSize, accuracy);
    }

    public static File commpressPicCycle2(File srcFile , long desFileSize, double accuracy) throws IOException{

        long srcFileSize = srcFile.length();
        if(srcFileSize <= desFileSize * 1024){
            return srcFile;
        }

        Thumbnails.of(srcFile).outputQuality(accuracy).scale(accuracy).toFile(srcFile);

        long fileSize = srcFile.length();
        //判断大小,如果小于500k,不压缩,如果大于等于500k,压缩
        if(fileSize <= desFileSize * 1024){
            return srcFile;
        }
        //如果不满足要求,递归直至满足小于1M的要求
       return commpressPicCycle2(srcFile, desFileSize, accuracy);
    }


    /**
     * 压缩覆盖原图， 返回base64
     */
    public static String compressSrcImageToBase64(String srcPath , long desFileSize){

        log.info("ImageCompressUtil compressSrcImageToBase64 srcPath: "+ srcPath );

        try {
//            String imgBase64 = ImageUtil.compressImgToBase64(srcPath, 1024, 768, desFileSize * 1024);
//            return "data:image/jpg;base64,"+imgBase64;

            //递归压缩,直到目标文件大小小于desFileSize
            File srcFile = new File(srcPath);
            long srcFileSize = srcFile.length();
            if(srcFileSize <= desFileSize * 1024){
                return imageToBase64(srcFile);
            }
//            log.info("ImageCompressUtil commpressPicCycle2 原图片:"+srcPath + ",大小:" + srcFileSize/1024 + "kb");
            File file = ImageCompressUtil.commpressPicCycle2(srcFile, desFileSize, 0.8);
//            log.info("ImageCompressUtil commpressPicCycle2 目标图片:" + srcPath + ",大小" + file.length()/1024 + "kb");
            return imageToBase64(file);
        } catch (Exception e) {
            log.info("ImageCompressUtil commpressPicForScale Exception: "+ e.getMessage());
        }
        return null;
    }


    /**
     * 压缩图片 返回base64， srcFile = destFile，覆盖原图
     */
    public static String compressImageToBase64(String srcPath , String destPath ,long desFileSize){
        try {
            File srcFile = new File(srcPath);
            long srcFileSize = srcFile.length();
            if(srcFileSize <= desFileSize * 1024){
                return imageToBase64(srcFile);
            }
            File destFile = new File(destPath);
//            log.info("ImageCompressUtil commpressPicCycle2 原图片:"+srcPath + ",大小:" + srcFileSize/1024 + "kb");
            File file = ImageCompressUtil.commpressPicCycle(srcFile, destFile, desFileSize, 0.8);
//            log.info("ImageCompressUtil commpressPicCycle2 目标图片:" + destPath + ",大小" + file.length()/1024 + "kb");
            return imageToBase64(file);
        } catch (IOException e) {
            log.info("ImageCompressUtil commpressPicForScale Exception: "+ e.getMessage());
        }
        return null;
    }


    public static void main(String args[]) throws Exception {

        String srcPath1 = "d:\\2.jpg";
//        System.out.println(ImageCompressUtil.imageToBase64(src1));
//        System.out.println(ImageCompressUtil.compressSrcImageToBase64(srcPath1, 500));
//        Thumbnails.of(srcPath1).scale(1f).outputQuality(0.2).toFile("d:\\11.jpg");
//        Thumbnails.of(srcPath1).scale(0.5).toFile("d:\\22.jpg");

//        String srcPath2 = "d:\\55.png";
//        File src2 = new File(srcPath2);
//        System.out.println(ImageCompressUtil.imageToBase64(src2));
//        System.out.println(ImageCompressUtil.compressImageToBase64(srcPath2,"d:\\66.png", 500));

    }



    private static boolean isBMP(byte[] buf){
        byte[] markBuf = "BM".getBytes();  //BMP图片文件的前两个字节
        return compare(buf, markBuf);
    }

    private static boolean isICON(byte[] buf) {
        byte[] markBuf = {0, 0, 1, 0, 1, 0, 32, 32};
        return compare(buf, markBuf);
    }
    private static boolean isWEBP(byte[] buf) {
        byte[] markBuf = "RIFF".getBytes(); //WebP图片识别符
        return compare(buf, markBuf);
    }

    private static boolean isGIF(byte[] buf) {

        byte[] markBuf = "GIF89a".getBytes(); //GIF识别符
        if(compare(buf, markBuf))
        {
            return true;
        }
        markBuf = "GIF87a".getBytes(); //GIF识别符
        if(compare(buf, markBuf))
        {
            return true;
        }
        return false;
    }


    private static boolean isPNG(byte[] buf) {

        byte[] markBuf = {(byte) 0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A}; //PNG识别符
        // new String(buf).indexOf("PNG")>0 //也可以使用这种方式
        return compare(buf, markBuf);
    }

    private static boolean isJPEGHeader(byte[] buf) {
        byte[] markBuf = {(byte) 0xff, (byte) 0xd8}; //JPEG开始符

        return compare(buf, markBuf);
    }

    private static boolean isJPEGFooter(byte[] buf)//JPEG结束符
    {
        byte[] markBuf = {(byte) 0xff, (byte) 0xd9};
        return compare(buf, markBuf);
    }

    /**
     * 获取文件的mimeType
     * @param filename
     * @return
     */
    public static String getMimeType(String filename){
        try {
            String mimeType = readType(filename);
            return String.format("image/%s", mimeType);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 检查图片类型
     * @param filename
     * @return
     */
    public static Boolean checkType(String filename)  {
        if(StringUtils.isBlank(filename)){
            return false;
        }
        int begin = filename.lastIndexOf(".");
        if(begin==-1){
            return false;
        }
        String fileType = filename.substring(begin).toLowerCase();
        switch (fileType){
            case ".jpg":
            case ".jpeg":
            case ".png":
            case ".gif":
            case ".webp":
            case ".bmp":
            case ".ico":
            return true;
        }
       return false;
    }

    /**
     * 读取文件类型
     * @param filename
     * @return
     * @throws IOException
     */
    private static String readType(String filename) throws IOException {

        FileInputStream fis = null;
        try {
            File f = new File(filename);
            if(!f.exists() || f.isDirectory() || f.length()<8) {
                throw new IOException("the file ["+f.getAbsolutePath()+"] is not image !");
            }
            fis= new FileInputStream(f);
            byte[] bufHeaders = readInputStreamAt(fis,0,8);
            if(isJPEGHeader(bufHeaders))
            {
                long skiplength = f.length()-2-8; //第一次读取时已经读了8个byte,因此需要减掉
                byte[] bufFooters = readInputStreamAt(fis, skiplength, 2);
                if(isJPEGFooter(bufFooters))
                {
                    return "jpeg";
                }
            }
            if(isPNG(bufHeaders))
            {
                return "png";
            }
            if(isGIF(bufHeaders)){

                return "gif";
            }
            if(isWEBP(bufHeaders))
            {
                return "webp";
            }
            if(isBMP(bufHeaders))
            {
                return "bmp";
            }
            if(isICON(bufHeaders))
            {
                return "ico";
            }
            int begin = filename.lastIndexOf('.');
            if(begin == -1){
                throw new IOException("不支持的图片格式!");
            }
            String fileType = filename.substring(begin).toLowerCase();
            throw new IOException("不支持" + fileType + "图片格式!");

        } catch (FileNotFoundException e) {
            throw e;
        }finally{
            try {
                if(fis!=null) fis.close();
            } catch (Exception e) {
            }
        }

    }


    /**
     * 标示一致性比较
     * @param buf  待检测标示
     * @param markBuf 标识符字节数组
     * @return 返回false标示标示不匹配
     */
    private static boolean compare(byte[] buf, byte[] markBuf) {
        for (int i = 0; i < markBuf.length; i++) {
            byte b = markBuf[i];
            byte a = buf[i];

            if(a!=b){
                return false;
            }
        }
        return true;
    }
    /**
     *
     * @param fis 输入流对象
     * @param skiplength 跳过位置长度
     * @param length 要读取的长度
     * @return 字节数组
     * @throws IOException
     */
    private static byte[] readInputStreamAt(FileInputStream fis, long skiplength, int length) throws IOException
    {
        byte[] buf = new byte[length];
        fis.skip(skiplength);  //
        int read = fis.read(buf,0,length);
        return buf;
    }
}
